use simple_logger::SimpleLogger;

#[ctor::ctor]
fn initialize() {
    SimpleLogger::new()
        .with_level(log::LevelFilter::Trace)
        .init()
        .expect("Failed to initialize logger");
}

enum LogLevel {
    Trace,
    Debug,
    Info,
    Warn,
    Error,
}

impl From<libc::c_uint> for LogLevel {
    fn from(input: libc::c_uint) -> Self {
        match input {
            0 => LogLevel::Trace,
            1 => LogLevel::Debug,
            2 => LogLevel::Info,
            3 => LogLevel::Warn,
            4 => LogLevel::Error,
            _ => panic!("{}", format!("Unsupported log level: {input}")),
        }
    }
}

fn log(message: &str, level: LogLevel) {
    match level {
        LogLevel::Trace => log::trace!("{}", message),
        LogLevel::Debug => log::debug!("{}", message),
        LogLevel::Info => log::info!("{}", message),
        LogLevel::Warn => log::warn!("{}", message),
        LogLevel::Error => log::error!("{}", message),
    }
}

pub mod unsafe_module {
    // The module's other content.

    /// Logs a message at the specified level.
    ///
    /// # Safety
    ///
    /// It is the caller's guarantee to ensure `message`:
    ///
    /// - is not a null pointer
    /// - points to valid, initialized data
    /// - points to memory ending in a null byte
    /// - won't be mutated for the duration of this function call
    #[no_mangle]
    pub unsafe extern "C" fn my_lib_log(message: *const libc::c_char, level: libc::c_uint) {
        let level: crate::LogLevel = level.into();

        // SAFETY: The caller has already guaranteed this is okay (see the
        // `# Safety` section of the doc-comment).
        let message_str: &str = match std::ffi::CStr::from_ptr(message).to_str() {
            Ok(val) => val,
            Err(err) => {
                crate::log(
                    &format!("FFI string conversion failed: {err}"),
                    crate::LogLevel::Error,
                );

                return;
            }
        };

        crate::log(message_str, level);
    }
}

#[cfg(test)]
mod tests {
    use crate::unsafe_module;
    use std::ffi::CString;

    #[test]
    fn trace() {
        let message = "testing LogLevel::Trace level";
        let c_string_message = CString::new(message).unwrap();
        let c_char_ptr: *const libc::c_char = c_string_message.as_ptr();
        unsafe {
            unsafe_module::my_lib_log(c_char_ptr, 0);
        };
    }

    #[test]
    fn debug() {
        let message = "testing LogLevel::Debug level";
        let c_string_message = CString::new(message).unwrap();
        let c_char_ptr: *const libc::c_char = c_string_message.as_ptr();
        unsafe {
            unsafe_module::my_lib_log(c_char_ptr, 1);
        };
    }

    #[test]
    fn info() {
        let message = "testing LogLevel::Info level";
        let c_string_message = CString::new(message).unwrap();
        let c_char_ptr: *const libc::c_char = c_string_message.as_ptr();
        unsafe {
            unsafe_module::my_lib_log(c_char_ptr, 2);
        };
    }

    #[test]
    fn warn() {
        let message = "testing LogLevel::Warn level";
        let c_string_message = CString::new(message).unwrap();
        let c_char_ptr: *const libc::c_char = c_string_message.as_ptr();
        unsafe {
            unsafe_module::my_lib_log(c_char_ptr, 3);
        };
    }

    #[test]
    fn error() {
        let message = "testing LogLevel::Error level";
        let c_string_message = CString::new(message).unwrap();
        let c_char_ptr: *const libc::c_char = c_string_message.as_ptr();
        unsafe {
            unsafe_module::my_lib_log(c_char_ptr, 4);
        };
    }

    #[test]
    #[should_panic(expected = "Unsupported log level: 5")]
    fn unsupported_log_level() {
        let message = "testing unsupported log level";
        let c_string_message = CString::new(message).unwrap();
        let c_char_ptr: *const libc::c_char = c_string_message.as_ptr();
        unsafe {
            unsafe_module::my_lib_log(c_char_ptr, 5);
        };
    }
}
